/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
    File:       QTSSAccessModule.cpp

    Contains:   Implementation of QTSSAccessModule. 
                    
    

*/

#include "QTSSAccessModule.h"

#include "defaultPaths.h"


#include "OSArrayObjectDeleter.h"
#include "QTSS_Private.h"
#include "StrPtrLen.h"
#include "OSMemory.h"
#include "MyAssert.h"
#include "StringFormatter.h"
#include "StrPtrLen.h"
#include "StringParser.h"
#include "base64.h"
#include "OS.h"
#include "AccessChecker.h"
#include "QTAccessFile.h"
#include "QTSSModuleUtils.h"

#ifndef __Win32__
#include <unistd.h>
#endif

#include <fcntl.h>
#include <errno.h>



// ATTRIBUTES

// STATIC DATA


#define MODPREFIX_ "modAccess_"

static StrPtrLen    sSDPSuffix(".sdp");
static OSMutex*     sUserMutex              = NULL;

static Bool16         sDefaultAuthenticationEnabled   = true;
static Bool16         sAuthenticationEnabled          = true;

static char* sDefaultUsersFilePath  = DEFAULTPATHS_ETC_DIR "qtusers";
static char* sUsersFilePath = NULL;

static char* sDefaultGroupsFilePath = DEFAULTPATHS_ETC_DIR "qtgroups";
static char* sGroupsFilePath = NULL;

static char* sDefaultAccessFileName = "qtaccess";

static QTSS_AttributeID sBadNameMessageAttrID               = qtssIllegalAttrID;
static QTSS_AttributeID sUsersFileNotFoundMessageAttrID     = qtssIllegalAttrID;
static QTSS_AttributeID sGroupsFileNotFoundMessageAttrID    = qtssIllegalAttrID;
static QTSS_AttributeID sBadUsersFileMessageAttrID          = qtssIllegalAttrID;
static QTSS_AttributeID sBadGroupsFileMessageAttrID         = qtssIllegalAttrID;

static QTSS_StreamRef           sErrorLogStream = NULL;
static QTSS_TextMessagesObject  sMessages = NULL;
static QTSS_ModulePrefsObject   sPrefs = NULL;
static QTSS_PrefsObject         sServerPrefs = NULL;

static AccessChecker**          sAccessCheckers;
static UInt32                   sNumAccessCheckers = 0;
static UInt32                   sAccessCheckerArraySize = 0;

static Bool16                   sAllowGuestDefaultEnabled = true;
static Bool16                   sDefaultGuestEnabled = true;

// FUNCTION PROTOTYPES

static QTSS_Error QTSSAccessModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
static QTSS_Error Register();
static QTSS_Error Initialize(QTSS_Initialize_Params* inParams);
static QTSS_Error Shutdown();
static QTSS_Error RereadPrefs();
static QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams);
static QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams);
static char*      GetCheckedFileName();

// FUNCTION IMPLEMENTATIONS


QTSS_Error QTSSAccessModule_Main(void* inPrivateArgs)
{
    return _stublibrary_main(inPrivateArgs, QTSSAccessModuleDispatch);
}


QTSS_Error  QTSSAccessModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
{
    switch (inRole)
    {
        case QTSS_Register_Role:
            return Register();
        break;
        
        case QTSS_Initialize_Role:
            return Initialize(&inParams->initParams);
        break;
        
        case QTSS_RereadPrefs_Role:
            return RereadPrefs();
        break;
        
        case QTSS_RTSPAuthenticate_Role:
              if (sAuthenticationEnabled)
                return AuthenticateRTSPRequest(&inParams->rtspAthnParams);
        break;
        
        case QTSS_RTSPAuthorize_Role:
              if (sAuthenticationEnabled)
                return AccessAuthorizeRTSPRequest(&inParams->rtspRequestParams);
        break;
            
        case QTSS_Shutdown_Role:
            return Shutdown();
        break;
    }
    
    return QTSS_NoErr;
}

QTSS_Error Register()
{
    // Do role & attribute setup
    (void)QTSS_AddRole(QTSS_Initialize_Role);
    (void)QTSS_AddRole(QTSS_RereadPrefs_Role);
    (void)QTSS_AddRole(QTSS_RTSPAuthenticate_Role);
    (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role);
        
    // Add AuthenticateName and Password attributes
    static char*        sBadAccessFileName  = "QTSSAccessModuleBadAccessFileName";
    static char*        sUsersFileNotFound  = "QTSSAccessModuleUsersFileNotFound";
    static char*        sGroupsFileNotFound = "QTSSAccessModuleGroupsFileNotFound";
    static char*        sBadUsersFile       = "QTSSAccessModuleBadUsersFile";
    static char*        sBadGroupsFile      = "QTSSAccessModuleBadGroupsFile";
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadAccessFileName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadAccessFileName, &sBadNameMessageAttrID);
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sUsersFileNotFound, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sUsersFileNotFound, &sUsersFileNotFoundMessageAttrID);
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sGroupsFileNotFound, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sGroupsFileNotFound, &sGroupsFileNotFoundMessageAttrID);
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadUsersFile, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadUsersFile, &sBadUsersFileMessageAttrID);
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadGroupsFile, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadGroupsFile, &sBadGroupsFileMessageAttrID);
    
    return QTSS_NoErr;
}


QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
    // Create an array of AccessCheckers
    sAccessCheckers = NEW AccessChecker*[2];
    sAccessCheckers[0] = NEW AccessChecker();
    sNumAccessCheckers = 1;
    sAccessCheckerArraySize = 2;

    // Setup module utils
    QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
    sErrorLogStream = inParams->inErrorLogStream;
    sMessages = inParams->inMessages;
    sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule);
    sServerPrefs = inParams->inPrefs;
    sUserMutex = NEW OSMutex();
    RereadPrefs();
    QTAccessFile::Initialize();
    
    return QTSS_NoErr;
}

QTSS_Error Shutdown()
{
    //cleanup
    
    // delete all the AccessCheckers
    UInt32 index;
    for(index = 0; index < sNumAccessCheckers; index++)
        delete sAccessCheckers[index];
    delete[] sAccessCheckers;
    sNumAccessCheckers = 0;
    
    // delete the main users and groups path
    
    //if(sUsersFilePath != sDefaultUsersFilePath) 
    // sUsersFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always
    // allocates memory even if it just returns the default value
    delete[] sUsersFilePath;
    sUsersFilePath = NULL;
    
    //if(sGroupsFilePath != sDefaultGroupsFilePath)
    // sGroupsFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always
    // allocates memory even if it just returns the default value
    delete[] sGroupsFilePath;
    sGroupsFilePath = NULL;
    
    return QTSS_NoErr;
}

char* GetCheckedFileName()
{
    char        *result = NULL;
    static char *badChars = "/'\"";
    char        theBadCharMessage[] = "' '";
    char        *theBadChar = NULL;
    result = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"qtaccessfilename", sDefaultAccessFileName);
    StrPtrLen searchStr(result);
    
    theBadChar = strpbrk(searchStr.Ptr, badChars);
    if ( theBadChar!= NULL) 
    {
        theBadCharMessage[1] = theBadChar[0];
        QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadNameMessageAttrID, 0, theBadCharMessage, result);
                
        delete[] result;
        result = NEW char[::strlen(sDefaultAccessFileName) + 2];
        ::strcpy(result, sDefaultAccessFileName);   
    }
    return result;
}

QTSS_Error RereadPrefs()
{
    OSMutexLocker locker(sUserMutex);
    
    //
    // Use the standard GetAttribute routine to retrieve the correct values for our preferences
    QTSSModuleUtils::GetAttribute(sPrefs, MODPREFIX_"enabled",    qtssAttrDataTypeBool16,
                          &sAuthenticationEnabled, &sDefaultAuthenticationEnabled, sizeof(sAuthenticationEnabled));
    
    //if(sUsersFilePath != sDefaultUsersFilePath)
    // sUsersFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always
    // allocates memory even if it just returns the default value
    // delete this old memory before reassigning it to new memory
    delete[] sUsersFilePath;
    sUsersFilePath = NULL;
        
    //if(sGroupsFilePath != sDefaultGroupsFilePath)
    // sGroupsFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always
    // allocates memory even if it just returns the default value
    // delete this old memory before reassigning it to new memory
    delete[] sGroupsFilePath;
    sGroupsFilePath = NULL;
    
    sUsersFilePath = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"usersfilepath", sDefaultUsersFilePath);
    sGroupsFilePath = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"groupsfilepath", sDefaultGroupsFilePath);
    // GetCheckedFileName always allocates memory
    char* accessFile = GetCheckedFileName();
    // QTAccessFile::SetAccessFileName makes its own copy, 
    // so delete the previous allocated memory after this call
    QTAccessFile::SetAccessFileName(accessFile);
    delete [] accessFile;
    
    if(sAccessCheckers[0]->HaveFilePathsChanged(sUsersFilePath, sGroupsFilePath))
    {
        sAccessCheckers[0]->UpdateFilePaths(sUsersFilePath, sGroupsFilePath);
        UInt32 err;
        err = sAccessCheckers[0]->UpdateUserProfiles();
        if(err & AccessChecker::kUsersFileNotFoundErr)
            QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, sUsersFilePath, NULL);
        else if(err & AccessChecker::kBadUsersFileErr)
            QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, sUsersFilePath, NULL);
        if(err & AccessChecker::kGroupsFileNotFoundErr)
            QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, sGroupsFilePath, NULL);
        else if(err & AccessChecker::kBadGroupsFileErr)
            QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, sGroupsFilePath, NULL);
    }

   QTSSModuleUtils::GetAttribute(sServerPrefs,"enable_allow_guest_default", qtssAttrDataTypeBool16, 
                                           &sAllowGuestDefaultEnabled,(void *)&sDefaultGuestEnabled, sizeof(sAllowGuestDefaultEnabled));
                                           

    return QTSS_NoErr;
}

QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams)
{
    QTSS_RTSPRequestObject  theRTSPRequest = inParams->inRTSPRequest;
    UInt32 fileErr;
    
    OSMutexLocker locker(sUserMutex);

    if  ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) )
        return QTSS_RequestFailed;

    // Get the user profile object from the request object
    QTSS_UserProfileObject theUserProfile = NULL;
    UInt32 len = sizeof(QTSS_UserProfileObject);
    QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len);
    Assert(len == sizeof(QTSS_UserProfileObject));
    if (theErr != QTSS_NoErr)
        return theErr;
        
    Bool16 defaultPaths = true;
    // Check for a users and groups file in the access file
    // For this, first get local file path and root movie directory
    //get the local file path
    char*   pathBuffStr = QTSSModuleUtils::GetLocalPath_Copy(theRTSPRequest);
    OSCharArrayDeleter pathBuffDeleter(pathBuffStr);
    if (NULL == pathBuffStr)
        return QTSS_RequestFailed;
    //get the root movie directory
    char*   movieRootDirStr = QTSSModuleUtils::GetMoviesRootDir_Copy(theRTSPRequest);
    OSCharArrayDeleter movieRootDeleter(movieRootDirStr);
    if (NULL == movieRootDirStr)
        return QTSS_RequestFailed;
    // Now get the access file path
    char* accessFilePath = QTAccessFile::GetAccessFile_Copy(movieRootDirStr, pathBuffStr);
    OSCharArrayDeleter accessFilePathDeleter(accessFilePath);
    // Parse the access file for the AuthUserFile and AuthGroupFile keywords
    char* usersFilePath = NULL;
    char* groupsFilePath = NULL;
        
        // Get the request action from the request object
        QTSS_ActionFlags action = qtssActionFlagsNoFlags;
        len = sizeof(action);
        theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAction, 0, (void*)&action, &len);
        Assert(len == sizeof(action));
        if (theErr != QTSS_NoErr)
            return theErr;
        
    // Allocates memory for usersFilePath and groupsFilePath
    QTSS_AuthScheme authScheme = QTAccessFile::FindUsersAndGroupsFilesAndAuthScheme(accessFilePath, action, &usersFilePath, &groupsFilePath);
    
    if((usersFilePath != NULL) || (groupsFilePath != NULL)) 
        defaultPaths = false;
        
    if(usersFilePath == NULL)
        usersFilePath = strdup(sUsersFilePath);
        
    if(groupsFilePath == NULL)
        groupsFilePath = strdup(sGroupsFilePath);
    
    OSCharArrayDeleter userPathDeleter(usersFilePath);
    OSCharArrayDeleter groupPathDeleter(groupsFilePath);
        
    AccessChecker* currentChecker = NULL;
    UInt32 index;
    
    // If the default users and groups file are not the ones we need
    if(!defaultPaths) 
    {
        // check if there is one AccessChecker that matches the needed paths
        // Don't have to check for the first one (or element zero) because it has the default paths
        for(index = 1; index < sNumAccessCheckers; index++)
        {
            // If an access checker that matches the users and groups file paths is found
            if(!sAccessCheckers[index]->HaveFilePathsChanged(usersFilePath, groupsFilePath))
            {
                currentChecker = sAccessCheckers[index];
                break;
            }                       
        }
        // If an existing AccessChecker for the needed paths isn't found
        if(currentChecker == NULL)
        {
            // Grow the AccessChecker array if needed
            if(sNumAccessCheckers == sAccessCheckerArraySize)
            {
                AccessChecker** oldAccessCheckers = sAccessCheckers;
                sAccessCheckers = NEW AccessChecker*[sAccessCheckerArraySize * 2];
                for(index = 0; index < sNumAccessCheckers; index++)
                {
                    sAccessCheckers[index] = oldAccessCheckers[index];
                }
                sAccessCheckerArraySize *= 2;
                delete [] oldAccessCheckers;
            }
        
            // And create a new AccessChecker for the paths
            sAccessCheckers[sNumAccessCheckers] = NEW AccessChecker();
            sAccessCheckers[sNumAccessCheckers]->UpdateFilePaths(usersFilePath, groupsFilePath);
            fileErr = sAccessCheckers[sNumAccessCheckers]->UpdateUserProfiles();
            
            if(fileErr & AccessChecker::kUsersFileNotFoundErr)
                QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, usersFilePath, NULL);
            else if(fileErr & AccessChecker::kBadUsersFileErr)
                QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, usersFilePath, NULL);
            if(fileErr & AccessChecker::kGroupsFileNotFoundErr)
                QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, groupsFilePath, NULL);
            else if(fileErr & AccessChecker::kBadGroupsFileErr)
                QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, groupsFilePath, NULL);
                
            currentChecker = sAccessCheckers[sNumAccessCheckers];
            sNumAccessCheckers++;
        }
        
    }
    else
    {
        currentChecker = sAccessCheckers[0];
    }
    
    // Before retrieving the user profile information
    // check if the groups/users files have been modified and update them otherwise
    fileErr = currentChecker->UpdateUserProfiles();
    
    /*
    // This is for logging the errors if users file and/or the groups file is not found or corrupted
    char* usersFile = currentChecker->GetUsersFilePathPtr();
    char* groupsFile = currentChecker->GetGroupsFilePathPtr();
    
    if(fileErr & AccessChecker::kUsersFileNotFoundErr)
        QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, usersFile, NULL);
    else if(fileErr & AccessChecker::kBadUsersFileErr)
        QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, usersFile, NULL);
    if(fileErr & AccessChecker::kGroupsFileNotFoundErr)
        QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, groupsFile, NULL);
    else if(fileErr & AccessChecker::kBadGroupsFileErr)
        QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, groupsFile, NULL);
      */

    // Retrieve the password data and group information for the user and set them
    // in the qtssRTSPReqUserProfile attr
    // The password data is crypt of the real password for Basic authentication
    // and it is MD5(username:realm:password) for Digest authentication
    
        // It the access file didn't contain an auth scheme, then get the auth scheme out of the request object
        // else, set the qtssRTSPReqAuthScheme to that found in the access file
        
        if (authScheme == qtssAuthNone)
        {
            // Get the authentication scheme from the request object
            len = sizeof(authScheme);
            theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, &len);
            Assert(len == sizeof(authScheme));
            if (theErr != QTSS_NoErr)
        return theErr;
    }
        else
        {
            theErr = QTSS_SetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, sizeof(authScheme));
            if (theErr != QTSS_NoErr)
        return theErr;
        }
        
    // Set the qtssUserRealm to the realm value retrieved from the users file
    // This should be used for digest auth scheme, and if no realm is found in the qtaccess file, then
    // it should be used for basic auth scheme.
    // No memory is allocated; just a pointer is returned
    StrPtrLen* authRealm = currentChecker->GetAuthRealm();
    (void)QTSS_SetValue(theUserProfile, qtssUserRealm, 0, (void*)(authRealm->Ptr), (authRealm->Len));
    
    
    // Get the username from the user profile object
    char*   usernameBuf = NULL;
    theErr = QTSS_GetValueAsString(theUserProfile, qtssUserName, 0, &usernameBuf);
    OSCharArrayDeleter usernameBufDeleter(usernameBuf);
    StrPtrLen username(usernameBuf);
    if (theErr != QTSS_NoErr)
        return theErr;
    
    // No memory is allocated; just a pointer to the profile is returned
    AccessChecker::UserProfile* profile = currentChecker->RetrieveUserProfile(&username);
    
    if(profile == NULL)
        return QTSS_NoErr;
        
    // Set the qtssUserPassword attribute to either the crypted password or the digest password
    // based on the authentication scheme
    if (authScheme == qtssAuthBasic) 
        (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)((profile->cryptPassword).Ptr), (profile->cryptPassword).Len);
    else if (authScheme == qtssAuthDigest)
        (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)((profile->digestPassword).Ptr), (profile->digestPassword).Len);    
    
    
    // Set the multivalued qtssUserGroups attr to the groups the user belongs to, if any
    UInt32 maxLen = profile->maxGroupNameLen;
    for(index = 0; index < profile->numGroups; index++) 
    {
        UInt32 curLen = ::strlen(profile->groups[index]);
        if(curLen < maxLen) 
        {
            char* groupWithPaddedZeros = NEW char[maxLen];  // memory allocated
            ::memcpy(groupWithPaddedZeros, profile->groups[index], curLen);
            ::memset(groupWithPaddedZeros+curLen, '\0', maxLen-curLen);
            (void)QTSS_SetValue(theUserProfile, qtssUserGroups, index, (void*)groupWithPaddedZeros, maxLen);
            delete [] groupWithPaddedZeros;                 // memory deleted
        }
        else 
        {
            (void)QTSS_SetValue(theUserProfile, qtssUserGroups, index, (void*)(profile->groups[index]), maxLen);
        }
    }
    
    return QTSS_NoErr;
}

QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{   
    Bool16 allowNoAccessFiles =  sAllowGuestDefaultEnabled; //no access files allowed means allowing guest access (unknown users)
    QTSS_ActionFlags noAction = ~qtssActionFlagsRead; // allow any action
    QTSS_ActionFlags authorizeAction =  QTSSModuleUtils::GetRequestActions(inParams->inRTSPRequest);
    Bool16 authorized =false;
    Bool16 allowAnyUser = false;
    QTAccessFile accessFile;
    return  accessFile.AuthorizeRequest(inParams,allowNoAccessFiles, noAction, authorizeAction, &authorized, &allowAnyUser);
}
